///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.circular.renderers
{
  import flash.display.Graphics;
  import flash.display.Sprite;
  import flash.geom.Point;
  import flash.geom.Rectangle;
  
  import ilog.gauges.gaugesClasses.circular.CircularValueRenderer;
  import ilog.utils.CSSUtil;
  import ilog.utils.MathUtil;
  
  import mx.graphics.IFill;
  import mx.graphics.IStroke;
  import mx.graphics.SolidColor;
  import mx.styles.CSSStyleDeclaration;
  
  /**
   * Specifies the stroke for the needle.
   * This style must be an instance of an implementation of the <code>IStroke</code> interface.
   * 
   * @default undefined 
   */
  [Style(name="stroke", type="mx.graphics.IStroke", inherit="no")]

  /**
   * Specifies the fill for the needle.
   * This style must be an instance of an implementation of the <code>IFill</code> interface.
   * 
   * @default 0xFF0000 
   */
  [Style(name="fill", type="mx.graphics.IFill", inherit="no")]

  /**
   * A default needle renderer for circular gauges.
   * 
   * @mxml
   *  
   * <p>The <code>&lt;ilog:NeedleRenderer&gt;</code> tag inherits all the properties
   * of its parent classes and adds the following properties:</p>
   *  
   * <pre>
   * &lt;ilog:NeedleRenderer
   *   <strong>Properties</strong>
   *   thickness="<i>5%</i>"
   *   baseRadius="<i>0%</i>" 
   *   pointRadius="<i>100%</i>"
   *   pointThickness="<i>95%</i>"
   * 
   *   <strong>Styles</strong>
   *   fill="<i>0xFF0000</i>"
   *   stroke="<i>No default</i>" 
   * /&gt;
   * </pre>
   *    
   * @see ilog.gauges.gaugesClasses.circular.CircularGaugeElement
   * @includeExample examples/NeedleRendererExample.mxml -noswf
   */
  public class NeedleRenderer extends CircularValueRenderer
  {
    /**
     * Constructor.
     */
    public function NeedleRenderer()
    {
      super();
    }

    /**
     *  @private
     */ 
    private const HALF_PI:Number = Math.PI / 2;
       
    /**
     *  @private
     */        
    private var _needleSprite:Sprite;

    /**
     *  @private
     */        
    private var _dirty:Boolean;
    
    /**
     *  @private
     */     
    private static var stylesInited:Boolean = initStyles(); 

    /**
     *  @private
     */ 
    private static function initStyles():Boolean
    {
      var nStyle:CSSStyleDeclaration =
        CSSUtil.createSelector("NeedleRenderer");   
            
      nStyle.defaultFactory = function():void  {
        this.adjustedRadius = 2;
        this.fill = new SolidColor(0xFF0000);
      }

      return true;
    }

    /**
     *  @private
     */    
    override protected function createChildren():void
    {
      if (_needleSprite == null) {
        _needleSprite = new Sprite();
        addChild(_needleSprite);
      }
    }

    /**
     *  @private
     */
    private var _pointRadius:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentPointRadius")]

    /**
     * Number that specifies the radius at which the point begins. If a percentage is
     * found, it is expressed relative to the total radius, see below. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>pointRadius</code> attribute, such as <code>pointRadius="95%"</code>,
     * but you cannot use a percentage value in the <code>pointRadius</code>
     * property in ActionScript.
     * Use the <code>percentPointRadius</code> property instead.</p>
     * 
     * @default NaN 
     */
    public function get pointRadius():Number
    {
      return _pointRadius;
    }

    /**
     *  @private 
     */
    public function set pointRadius(value:Number):void
    {
      if (_pointRadius == value)
        return;
      _pointRadius = value;
      if (! isNaN(value))
        _percentPointRadius = NaN;      
      _dirty = true;
      invalidateDisplayList();
    }

    /**
     *  @private
     */    
    private var _percentPointRadius:Number = 95;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the position of the point as a percentage
     * of the radius.
     *  
     * @default 95.
     */
    public function get percentPointRadius():Number
    {
      return _percentPointRadius;
    }

    /**
     *  @private
     */
    public function set percentPointRadius(value:Number):void
    {
      _percentPointRadius = value;
      if (! isNaN(value))
        _pointRadius = NaN;      
      _dirty = true;
      invalidateDisplayList();
    }

    /**
     *  @private
     */
    private var _baseRadius:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentBaseRadius")]

    /**
     * Number that specifies the radius at which the base begins. If a percentage is
     * found, it is expressed relative to the total radius, see below. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>baseRadius</code> attribute, such as <code>baseRadius="50%"</code>,
     * but you cannot use a percentage value in the <code>baseRadius</code>
     * property in ActionScript.
     * Use the <code>percentBaseRadius</code> property instead.</p>
     * 
     * @default NaN 
     */
    public function get baseRadius():Number
    {
      return _baseRadius;
    }

    /**
     *  @private 
     */
    public function set baseRadius(value:Number):void
    {
      if (_baseRadius == value)
        return;
      if (! isNaN(value))
        _percentBaseRadius = NaN;        
      _baseRadius = value;
      _dirty = true;
      invalidateDisplayList();
    }

    /**
     *  @private
     */    
    private var _percentBaseRadius:Number = 0;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the position of the base as a percentage
     * of the radius.
     *  
     * @default 95.
     */
    public function get percentBaseRadius():Number
    {
      return _percentBaseRadius;
    }

    /**
     *  @private
     */
    public function set percentBaseRadius(value:Number):void
    {
      _percentBaseRadius = value;
      if (! isNaN(value))
        _baseRadius = NaN;      
      _dirty = true;
      invalidateDisplayList();
    }

    /**
     *  @private
     */
    private var _pointThickness:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentPointThickness")]

    /**
     * Number that specifies the thickness of the point. If a percentage is
     * found, it is expressed relative to the total thickness, see below. The thickness
     * of the needle base varies from <code>thickness</code> to 
     * <code>pointThickness</code>. The thickness of the needle point
     * varies from <code>pointThickness</code> to zero.
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>pointThickness</code> attribute, such as <code>pointThickness="95%"</code>,
     * but you cannot use a percentage value in the <code>pointThickness</code>
     * property in ActionScript.
     * Use the <code>percentPointThickness</code> property instead.</p>
     * 
     * @default NaN 
     */
    public function get pointThickness():Number
    {
      return _pointThickness;
    }

    /**
     *  @private
     */
    public function set pointThickness(value:Number):void
    {
      if (_pointThickness == value)
        return;
      _pointThickness = value;
      if (! isNaN(value))
        _percentPointThickness = NaN;
      _dirty = true;
      invalidateDisplayList();      
    }

    /**
     *  @private
     */    
    private var _percentPointThickness:Number = 100;
    
    [Inspectable(environment="none")]

    /**
     * Number that specifies the thickness of the point as a percentage
     * of thickness. 
     * 
     * @default 100.
     */
    public function get percentPointThickness():Number
    {
      return _percentPointThickness;
    }

    /**
     *  @private
     */
    public function set percentPointThickness(value:Number):void
    {
      _percentPointThickness = value;
      if (! isNaN(value))
        _pointThickness = NaN;      
      _dirty = true;
      invalidateDisplayList();
    }

    /**
     *  @private
     */
    private var _thickness:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentThickness")]

    /**
     * Number that specifies the thickness of the needle. If a percentage is
     * found, it is expressed relative to the radius, see below. The thickness
     * of the needle base varies from <code>thickness</code> to 
     * <code>pointThickness</code>. The thickness of the needle point 
     * varies from <code>pointThickness</code> to zero.
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>thickness</code> attribute, such as <code>thickness="15%"</code>,
     * but you cannot use a percentage value in the <code>thickness</code>
     * property in ActionScript.
     * Use the <code>percentThickness</code> property instead.</p>
     * 
     * @default NaN 
     */
    public function get thickness():Number
    {
      return _thickness;
    }

    /**
     *  @private
     */
    public function set thickness(value:Number):void
    {
      if (_thickness == value)
        return;
      if (! isNaN(value))
        _percentThickness = NaN;        
      _thickness = value;
      _dirty = true;
      invalidateDisplayList();
    }

    /**
     *  @private
     */    
    private var _percentThickness:Number = 5;

    [Inspectable(environment="none")]
    
    /**
     * Number that specifies the thickness of the needle as a percentage
     * of the radius. 
     * 
     * @default 5.
     */
    public function get percentThickness():Number
    {
      return _percentThickness;
    }

    /**
     *  @private
     */
    public function set percentThickness(value:Number):void
    {
      _percentThickness = value;
      if (! isNaN(value))
        _thickness = NaN;      
      _dirty = true;
      invalidateDisplayList();
    }
    
    /**
     *  @private
     */    
    private function getThickness():Number
    {
      if (!isNaN(thickness))
        return thickness;
      else
        return percentThickness * getRadius() / 100;
    }

    /**
     *  @private
     */
    private function getPointThickness():Number
    {
      if (!isNaN(pointThickness))
        return pointThickness;
      else
        return percentPointThickness * getThickness() / 100;
    }

    /**
     * @private
     */
    private function getPointRadius():Number
    {
      if (! isNaN(pointRadius))
        return pointRadius;
      else
        return percentPointRadius * getRadius() / 100;
    }

    /**
     * @private
     */
    private function getBaseRadius():Number
    {
      if (! isNaN(baseRadius))
        return baseRadius;
      else
        return percentBaseRadius * getRadius() / 100;
    }
    
    /**
     * The function that draws the needle. A correct override of this function 
     * has to follow some constraints:
     * <ul>
     *     <li> The point of the needle has to be on the right of the origin of the sprite.</li>
     *     <li> The (0, 0) point in the sprite coordinate must represent the rotation center of the needle.</li>
     * </ul>
     * 
     * @param needle The Sprite into which the needle must be drawn.
     * @param radius The total length of the needle, in pixels.
     * @param baseRadius The starting radius of the needle. 
     * @param needleWidth The width, in pixels, of the needle at the needle's base origin.
     * @param pointWidth The width, in pixels, of the needle's point origin.
     * @param pointHeight The height, in pixels, of the needle's point.
     * @param fill The fill to use to fill the needle.
     * @param stroke The stroke to use to draw the needle outline.
     */ 
    protected function drawNeedle(needle:Sprite, radius:Number, baseRadius:Number, needleWidth:Number, 
                                  pointWidth:Number, pointHeight:Number, 
                                  fill:IFill, stroke:IStroke):void
    {
      var g:Graphics = needle.graphics;
      
      g.clear();
      if (stroke)
        stroke.apply(g);
      if (fill)
        fill.begin(g, new Rectangle(0, -needleWidth/2, radius, needleWidth));
      g.moveTo(baseRadius, - needleWidth/2);
      g.lineTo(pointHeight, - pointWidth/2);
      g.lineTo(radius, 0);
      g.lineTo(pointHeight, + pointWidth/2);
      g.lineTo(baseRadius, needleWidth/2);
      g.lineTo(baseRadius, 0);
      if (fill)
        fill.end(g);
    }
    
    /**
     * @private
     */
    override public function styleChanged(styleProp:String):void
    {
      super.styleChanged(styleProp);
      _dirty = true;
    }
    
    /**
     *  @private
     */
    private var _previousWidth:Number;

    /**
     *  @private
     */
    private var _previousHeight:Number;
    
    /**
     *  @private
     */    
    private function sameSize(w:Number, h:Number):Boolean
    {
      if (w == _previousWidth && h == _previousHeight) {
        return true;
      } else {
        _previousWidth = w;
        _previousHeight = h;
        return false;
      }
    }

    /**
     * Draws the needle by calling <code>drawNeedle</code>.     
     *
     * @param unscaledWidth Specifies the width, in pixels, of the component
     * in the component's coordinates, regardless of the value of the
     * <code>scaleX</code> property of the component.
     *
     * @param unscaledHeight Specifies the height, in pixels, of the component
     * in the component's coordinates, regardless of the value of the
     * <code>scaleY</code> property of the component.
     * 
     * @see #drawNeedle()
     */
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);
      if (!scale)
        return;
      if (!sameSize(unscaledWidth, unscaledHeight) || _dirty)
        drawNeedle(_needleSprite, getRadius(), getBaseRadius(), getThickness(), 
                   getPointThickness(), getPointRadius(), 
                   getStyle("fill"), getStyle("stroke"));

      var o:Point = getOrigin();
      var a:Number;
      
      if (isNaN(transitionAngle))
        a = scale.angleForValue(value);
      else
        a = transitionAngle;
        
      _needleSprite.x = o.x;
      _needleSprite.y = o.y;
      _needleSprite.rotation = 360 - MathUtil.toDegrees(a);    
      _dirty = false;  
    }
  }
}
